package com.bruce.tool.office.excel.core;

import com.bruce.tool.common.exception.BaseRuntimeException;
import com.bruce.tool.common.util.ClassUtils;
import com.bruce.tool.common.util.DateUtils;
import com.bruce.tool.common.util.file.IOUtils;
import com.bruce.tool.common.util.string.StringUtils;
import com.bruce.tool.office.excel.annotation.Header;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 功能 :
 * 导入/导出
 * @author : Bruce(刘正航) 3:06 PM 2018/12/7
 */
@Slf4j
@NoArgsConstructor(staticName = "create")
public class CSV {

    private static final int TIMESTAMP_LENGTH_MYSQL = 10;
    private static final int TIMESTAMP_LENGTH_JAVA = 13;
    /**输入流**/
    private InputStream in;
    /**数据对象**/
    private Class clazz;
    /**格式化表头**/
    private String titles;
    /**总数据**/
    private List datas = Lists.newArrayList();
    /**表头:用于控制表头字段顺序**/
    private List<String> headerTitles = Lists.newArrayList();
    /**字段:用于控制表头字段顺序**/
    private List<String> headerNames = Lists.newArrayList();
    /**字段和表头匹配关系(key=表头,value=字段)**/
    private Map<String,String> headerPairs = Maps.newHashMap();
    /**用于格式化列数据**/
    private List<String> formats = Lists.newArrayList();

    //////////////////////////////////////////////////////////////////////////////////////////////////

    public CSV stream(InputStream in){
        this.in = in;
        return this;
    }

    /**设置数据源**/
    public CSV datas(List datas){
        this.datas = datas;
        return this;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////

    /**增加表头数据**/
    public CSV titles(Class clazz){
        this.clazz = clazz;
        return this;
    }

    /**增加表头数据**/
    public CSV titles(String titles){
        this.titles = titles;
        return this;
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////

    /**导入**/
    public List importFromStream(){
        initTitles();
        try {
            CsvReader csvReader = new CsvReader(this.in, ',', Charset.forName("GBK"));
            // 读表头
            csvReader.readHeaders();
            // 获取表头
            String[] headers = csvReader.getHeaders();

            readLineDatas(csvReader, headers);

        } catch (Exception e) {
            log.error("{}",e);
        }
        return this.datas;
    }

    /**读取行数据**/
    private void readLineDatas(CsvReader csvReader, String[] headers) throws IOException, InstantiationException, IllegalAccessException {
        while (csvReader.readRecord()) {
            Object t;
            if( null != this.clazz){
                t = this.clazz.newInstance();
            }else if(StringUtils.isNotBlank(this.titles)){
                t = Maps.newHashMap();
            }else{
                t = Maps.newHashMap();
            }
            for (int i = 0,length = headers.length; i < length; i++) {
                String title = headers[i];
                String name = headerPairs.get(title);
                String value = csvReader.get(title);
                if( null != this.clazz ){
                    ClassUtils.setValue(t,name,value);
                }else if(t instanceof Map){
                    ((Map) t).put(name, value);
                }
            }
            this.datas.add(t);
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////

    /**导出到response中**/
    public void toResponse(HttpServletResponse response, String fileName){
        //1.设置文件ContentType类型，这样设置，会自动判断下载文件类型
        response.setContentType("application/form-data");
        try {
            // 转码中文
            fileName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
            // TODO: 2017/4/27 文件扩展名
            response.setHeader("Content-Disposition", "attachment;fileName=" + fileName + ".csv");

            this.toStream(response.getOutputStream());

        } catch (Exception e) {
            log.error("{}",e);
        }
    }

    /**导出到字节数组**/
    public byte[] toBytes(OutputStream out){
        try(ByteArrayOutputStream bos = new ByteArrayOutputStream()){
            this.toStream(out);
            return bos.toByteArray();
        } catch (Exception e) {
            log.error("{}",e);
        }finally {
            IOUtils.closeQuietly(out);
        }
        return new byte[0];
    }

    /**导出:执行该方法前,需要先执行stream()**/
    public void toStream(OutputStream out){
        initTitles();
        try {
            CsvWriter writer = new CsvWriter(out, ',', Charset.forName("GBK"));
            // 写表头
            writer.writeRecord(headerTitles.toArray(new String[]{}));
            // 写行数据
            writeLineDatas(writer);
            // 最后刷新缓存
            writer.flush();
        } catch (Exception e) {
            log.error("{}",e);
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////

    /**转换字符串为表头**/
    private void initTitles() {
        initTitlesByString(this.titles);
        initTitlesByClass(this.clazz);
    }

    /**用类数据初始化表头**/
    private void initTitlesByClass(Class clazz) {
        if( null == clazz ){
            return;
        }
        List<Field> fields = ClassUtils.getAllFields(clazz);
        for (Field m : fields) {
            if (m.isAnnotationPresent(Header.class)) {
                Header anno = m.getAnnotation(Header.class);
                headerTitles.add(anno.name());
                headerNames.add(m.getName());
                headerPairs.put(anno.name(),m.getName());
                formats.add(anno.format());
            }
        }
    }

    /**使用格式化字符串-初始化表头**/
    private void initTitlesByString(String titles) {
        if(StringUtils.isBlank(titles)){
            return;
        }
        List<String> list = StringUtils.splitToList(titles,",");
        if( !CollectionUtils.isEmpty(list) ){
            for (String title : list) {
                String[] titleArray = title.split(":",3);
                List<String> values = Arrays.asList(titleArray);
                if(!CollectionUtils.isEmpty(values) && values.size() == 2){
                    headerTitles.add(values.get(0));
                    headerNames.add(values.get(1));
                    headerPairs.put(values.get(0),values.get(1));
                    formats.add("");
                }else
                if(!CollectionUtils.isEmpty(values) && values.size() == 3){
                    headerTitles.add(values.get(0));
                    headerNames.add(values.get(1));
                    headerPairs.put(values.get(0),values.get(1));
                    formats.add(values.get(2));
                }
            }
        }
    }

    /**格式化单元格值**/
    private boolean formatValue(List<String> values, int j, Object value) {
        if( CollectionUtils.isEmpty(this.formats) ){
            return false;
        }
        String format = formats.get(j);
        if( StringUtils.isNotBlank(format) && null != value ){
            if( String.valueOf(value).length() == TIMESTAMP_LENGTH_MYSQL) {
                String date = DateUtils.format(DateUtils.create(Long.valueOf(String.valueOf(value)) * 1000));
                values.add(String.valueOf(date));
                return true;
            }
            if( String.valueOf(value).length() == TIMESTAMP_LENGTH_JAVA){
                String date = DateUtils.format(DateUtils.create(Long.valueOf(String.valueOf(value))));
                values.add(String.valueOf(date));
                return true;
            }
        }
        return false;
    }

    /**写行数据**/
    private void writeLineDatas(CsvWriter writer) throws IOException {
        int i = 0;
        for (Object obj : this.datas) {
            List<String> values = fetchLineData(obj);
            writer.writeRecord(values.toArray(new String[]{}));
            i++;
            if( i > 0 && i % 10000 == 0 ){
                writer.flush();
            }
        }
    }

    /**获取行数据**/
    private List<String> fetchLineData(Object obj) {
        List<String> values = Lists.newArrayList();
        for (int j = 0,length = headerNames.size();j<length;j++) {
            String header = headerNames.get(j);
            try {
                Object value;
                if( obj instanceof Map ){
                    value = ((Map)obj).get(header);
                }else{
                    value = PropertyUtils.getSimpleProperty(obj, header);
                }
                if (formatValue(values, j, value)) { continue; }
                values.add(String.valueOf(value));
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                throw new BaseRuntimeException(e);
            }
        }
        return values;
    }
}
